#include <iostream>
#include "cstring"
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <set>
#include <map>
#include <iomanip>
#include <unordered_map>
#include <sstream>

#include "unittest.h"
#include "util.h"
#include "DataSource.h"
#include "PublicRandomTaskAssignment.h"
#include "DifferentialPrivacyTaskAssignment.h"
#include "EncryptedDirectTaskAssignment.h"
#include "LightPrivateTaskAssignment.h"

#define len(array) (sizeof(array) / sizeof(array[0]))

using namespace std;

const int ID_COUNT = 100000;

typedef const char *ID;

typedef unordered_map<Fingerprint, ID, fingerprint_hash, fingerprint_equal> FingerprintIDMap;

int mod(int x, int y, int p) {
    //x^y % p
    int mod = x;
    while (y > 1) {
        mod = (mod * x) % p;
        y--;
    }
    return mod;
}

template<class T>
void swap(T *arr, int i, int j) {
    T tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

set<string> randomSelect(set<string> idSet, int count, int seed) {
    srand(seed);
    set<string> selection;
    string *strList = new string[idSet.size()];
    int i = 0;
    for (const auto &item : idSet) {
        strList[i++] = item;
    }
    for (int i = 0; i < idSet.size(); i++) {
        swap(strList, rand() % idSet.size(), rand() % idSet.size());
    }
    for (int i = 0; i < count; i++) {
        selection.insert(strList[i]);
    }
    return selection;
}

int *randomSelect(int count, int *arr, int len, int seed) {
    int *copy = new int[len];
    memcpy(copy, arr, len * sizeof(int));
    srand(seed);
    int *result = new int[count];
    for (int i = 0; i < len; i++) {
        swap(copy, rand() % len, rand() % len);
    }
    memcpy(result, copy, count * sizeof(int));
    delete[] copy;
    return result;
}


inline unsigned char rotateLeft(unsigned char c) {
    return (c >> 7) | (c << 1);
}

int substring(const char *x, int fy, int yLen, int p) {
    return 0;
}

const char *substring(const char *str, int start, int len) {
    char *res = new char[len + 1];
    memcpy(res, str + start, len);
    res[len] = 0;
    return res;
}

//ID validateIdentityOnIncomingFingerprint(const char *x,
//                                         int xLen,
//                                         FingerprintIDMap &fingerprintIDMap,
//                                         const Fingerprint &fy,
//                                         const int primeSuite[]) {
////    cout << "Validating " << fy << endl;
//    auto itr = fingerprintIDMap.find(fy);
//    if (itr != fingerprintIDMap.end()) {
//        return itr->second;
//    }
//    int n = xLen * 8, m = ID_LENGTH * 8;
//    int j = 0;
//    Fingerprint fx = computeFingerprint(x, ID_LENGTH, primeSuite);
//    int *wp = new int[PRIME_COUNT];
//    for (int p = 0; p < PRIME_COUNT; p++) {
//        wp[p] = mod(2, m, primeSuite[p]);
////        wp[p] = static_cast<int>(pow(2.0, m)) % primeSuite[p]; //wp = 2^m % p
//    }
//    while (j <= n - m + 1) {
//        if (fingerprint_equal()(fx, fy)) {
//            delete[] wp;
//            return substring(x, j / 8, ID_LENGTH);
//        } else {
//            /**
//             * 00110001 00110010 00110011
//             * 00110100 00110101 00110110
//             * b：0110 0010
//             * 1100 0100
//             */
//            for (int i = 0; i < ID_LENGTH; i++) {
//                unsigned char copyXj = *(x + j / 8);
//                unsigned char copyXjm = *(x + j / 8 + ID_LENGTH);
//                for (int k = 0; k < 8; k++) {
//                    copyXj = rotateLeft(copyXj);
//                    copyXjm = rotateLeft(copyXjm);
//                    for (int p = 0; p < PRIME_COUNT; p++) {
//                        int t = (fx.values[p] << 1) + (copyXjm & 0x01);
//                        if (copyXj & 0x01) {
//                            t -= wp[p];
//                        }
//                        if (t < 0) {
//                            fx.values[p] = t + primeSuite[p];
//                        } else if (t < primeSuite[p]) {
//                            fx.values[p] = t;
//                        } else {
//                            fx.values[p] = t - primeSuite[p];
//                        }
//                    }
//                    j++;
//                }
////                cout << endl;
//            }
//            fingerprintIDMap[fx] = substring(x, j / 8, ID_LENGTH);
//        }
//    }
//    cout << fy << " not found!!!" << endl;
//    delete[] wp;
//    return nullptr;
//}

//ID validateIdentityPreparingFingerprint(const FingerprintIDMap &fingerprintIDMap,
//                                        const Fingerprint &fp) {
//    auto itr = fingerprintIDMap.find(fp);
//    if (itr == fingerprintIDMap.end()) {
//        return nullptr;
//    } else {
//        return itr->second;
//    }
//}
//
//void testFingerprint() {
//    int *primeList = new int[100];
//    int primeListLength = generatePrimeList(primeList, 100);
//    int *primeSuite = randomSelect(PRIME_COUNT, primeList, primeListLength, 0);
//    int *primeSuite = new int[4]{3, 7, 13, 53};
//    cout << computeFingerprint("123", ID_LENGTH, primeSuite) << endl; //"1" : 49
//    cout << computeFingerprint("12", 53) << endl; //"12" : 49 * 256 + 50 = 12594, 12594 % 53 = 33
//    cout << computeFingerprint("123", 53) << endl; //"123": (49 * 256 * 256 + 50 * 256 + 51) % 53 = 19
//}
//
//void testCompareMentocarloAndFingerprintAll() {
//    cout << "init" << endl;
//    int *primeList = new int[100];
//    int count = generatePrimeList(primeList, 100);
//    int *primeSuite = randomSelect(PRIME_COUNT, primeList, count, 0);
//    delete[] primeList;
//    cout << "PrimeSuite: ";
//    for (int i = 0; i < PRIME_COUNT; i++) {
//        cout << primeSuite[i] << ",";
//    }
//    cout << endl;
//    //生成10个字符的字符串100000个
//    set<string> idSet = generateRandomIds(ID_COUNT, ID_LENGTH, 1);
//    set<string> selection = randomSelect(idSet, 5000, 1);
//    Fingerprint *fySet = new Fingerprint[selection.size()];
//    auto selectionItr = selection.begin();
//    for (int i = 0; i < selection.size(); i++) {
//        fySet[i] = computeFingerprint((*selectionItr).c_str(), ID_LENGTH, primeSuite);
//        selectionItr++;
//    }
//    cout << "***********Method1***********" << endl;
//    cout << "Computing fingerprints of each ID" << endl;
//    time_t start = time(NULL);
//    FingerprintIDMap map;
//    for (const auto &id : idSet) {
//        map[computeFingerprint(id.c_str(), ID_LENGTH, primeSuite)] = id.c_str();
//    }
//    cout << map.size() << endl;
//    cout << "Validate " << selection.size() << " ID " << endl;
//    for (int i = 0; i < selection.size(); i++) {
//        ID id = validateIdentityPreparingFingerprint(map, fySet[i]);
//        cout << id << " ";
//    }
//    time_t end = time(NULL);
//    cout << endl;
//    cout << end - start << "ms" << endl;
////    2。把所有的字符串连成一个超长字符串
//    map.clear();
//    cout << "***********Method2***********" << endl;
//    start = time(NULL);
//    cout << "Concating all of the ID" << endl;
//    int xLen = ID_COUNT * ID_LENGTH;
//    char *x = new char[xLen];
//    int xOffset = 0;
//    for (const auto &id : idSet) {
//        memcpy(x + xOffset, id.c_str(), ID_LENGTH);
//        xOffset += ID_LENGTH;
//    }
//    cout << "Validate " << selection.size() << " ID " << endl;
//    for (int i = 0; i < selection.size(); i++) {
//        ID id = validateIdentityOnIncomingFingerprint(x, xLen, map, fySet[i], primeSuite);
//        cout << id << " ";
//    }
//    cout << endl;
//    end = time(NULL);
//    cout << end - start << "ms" << endl;
//
//void testGenerateIds() {
//    set<string> idSet = generateRandomIds(10, 10, 1);
//    for (auto it = idSet.begin(); it != idSet.end(); it++) {
//        cout << *it << endl;
//    }
//}

void testTransmissionChaningTask(DataSource *data) {
    ofstream taskCsv("task.csv");
    int taskCount[]{20, 40, 60, 80, 100, 120, 140, 160};
    taskCsv << "method";
    for (int i = 0; i < 8; i++) {
        taskCsv << "," << taskCount[i];
    }
    taskCsv << endl;
    vector<Test *> tests;
    tests.push_back(new PRTATest(data, 0));
    tests.push_back(new DPTATest(data, 3, 1500, 1));
    tests.push_back(new DPTATest(data, 3, 1500, 5));
    tests.push_back(new EDTATest(data));
    tests.push_back(new LPTATest(data, 4, 1122, 3));
    tests.push_back(new LPTATest(data, 4, 1122, 4));
    tests.push_back(new LPTATest(data, 1, 1122, 3));
    tests.push_back(new LPTATest(data, 1, 1122, 4));
    for (auto &test:tests) {
        taskCsv << test->name();
        for (int i : taskCount) {
            data->allocate(200, i);
            test->start();
            taskCsv << "," << test->transmission;
        }
        taskCsv << endl;
    }
    taskCsv.close();
}

void testTransmissionChangingWorker(DataSource *data) {
    ofstream workerCsv("worker.csv");
    int workerCount[]{50, 100, 150, 200, 300, 400, 500, 1000};
    workerCsv << "method";
    for (int i = 0; i < 8; i++) {
        workerCsv << "," << workerCount[i];
    }
    workerCsv << endl;
    vector<Test *> tests;
    tests.push_back(new PRTATest(data, 0));
    tests.push_back(new DPTATest(data, 3, 1500, 1));
    tests.push_back(new DPTATest(data, 3, 1500, 5));
    tests.push_back(new EDTATest(data));
    tests.push_back(new LPTATest(data, 4, 1122, 3));
    tests.push_back(new LPTATest(data, 4, 1122, 4));
    tests.push_back(new LPTATest(data, 1, 1122, 3));
    tests.push_back(new LPTATest(data, 1, 1122, 4));
    for (auto &test:tests) {
        workerCsv << test->name();
        for (int i : workerCount) {
            data->allocate(i, 200);
            test->start();
            workerCsv << "," << test->transmission;
        }
        workerCsv << endl;
    }
    workerCsv.close();
}

void testTimeChangingWorker(DataSource *data) {
    ofstream workerCsv("time_worker.csv");
    int workerCount[]{50, 100, 150, 200, 300, 400, 500, 1000};
    workerCsv << "method";
    for (int i = 0; i < 8; i++) {
        workerCsv << "," << workerCount[i];
    }
    workerCsv << endl;
    vector<Test *> tests;
    tests.push_back(new PRTATest(data, 0));
    tests.push_back(new DPTATest(data, 3, 1500, 1));
    tests.push_back(new DPTATest(data, 3, 1500, 5));
    tests.push_back(new EDTATest(data));
    tests.push_back(new LPTATest(data, 4, 1122, 3));
    tests.push_back(new LPTATest(data, 4, 1122, 4));
    tests.push_back(new LPTATest(data, 1, 1122, 3));
    tests.push_back(new LPTATest(data, 1, 1122, 4));
    for (auto &test:tests) {
        workerCsv << test->name();
        for (int i : workerCount) {
            data->allocate(i, 200);
            cout << test->name() << endl;
            test->start();
            workerCsv << "," << test->timeMetric;
        }
        workerCsv << endl;
    }
    workerCsv.close();
}

void testTimeChangingTask(DataSource *data) {
    ofstream taskCsv("time_task.csv");
    int taskCount[]{20, 40, 60, 80, 100, 120, 140, 160};
    taskCsv << "method";
    for (int i = 0; i < 8; i++) {
        taskCsv << "," << taskCount[i];
    }
    taskCsv << endl;
    vector<Test *> tests;
//    tests.push_back(new PRTATest(data, 0));
//    tests.push_back(new DPTATest(data, 3, 1500, 1));
    tests.push_back(new DPTATest(data, 3, 1500, 5));
//    tests.push_back(new EDTATest(data));
//    tests.push_back(new LPTATest(data, 4, 1122, 3));
//    tests.push_back(new LPTATest(data, 4, 1122, 4));
//    tests.push_back(new LPTATest(data, 1, 1122, 3));
//    tests.push_back(new LPTATest(data, 1, 1122, 4));
    for (auto &test:tests) {
        taskCsv << test->name();
        for (int i : taskCount) {
            data->allocate(200, i);
            cout << test->name() << endl;
            test->start();
            taskCsv << "," << test->timeMetric;
        }
        taskCsv << endl;
    }
    taskCsv.close();
}

void testTravelCostChangingWorker(DataSource *data) {
    ofstream ratioCsv("ratio_worker.csv");
    ofstream workerCsv("cost_worker.csv");
    int workerCount[]{200, 300, 400, 500, 600,700};
    workerCsv << "method";
    ratioCsv << "method";
    for (int i = 0; i < 6; i++) {
        workerCsv << "," << workerCount[i];
        ratioCsv << "," << workerCount[i];
    }
    workerCsv << endl;
    ratioCsv << endl;
    vector<Test *> tests;
    tests.push_back(new PRTATest(data, 123321, 'p'));
    tests.push_back(new PRTATest(data, 123321, 't'));
    tests.push_back(new PRTATest(data, 123321, 'k'));
    tests.push_back(new DPTATest(data, 3, 1000, -1));
    tests.push_back(new LPTATest(data, 4, 123321, 4));
    for (auto &test:tests) {
        workerCsv << test->name();
        ratioCsv << test->name();
        for (int wc : workerCount) {
            data->allocate(wc, 100);
            cout << test->name() << endl;
            test->start()->calculateTravelCost()->calculateAssignedTaskRatio();
            workerCsv << "," << fixed << setprecision(2) << (test->travelCost / test->server->assignedTaskCount);
            ratioCsv << "," << fixed << setprecision(2) << test->server->assignedTaskCount;
        }
        workerCsv << endl;
        ratioCsv << endl;
    }
    ratioCsv.close();
    workerCsv.close();
}

void testTravelCostChangingTask(DataSource *data) {
    ofstream ratioCsv("ratio_task.csv");
    ofstream taskCsv("cost_task.csv");
    int taskCount[]{10, 20, 30, 40, 50, 60};
    taskCsv << "method";
    ratioCsv << "method";
    for (int i = 0; i < 6; i++) {
        taskCsv << "," << taskCount[i];
        ratioCsv << "," << taskCount[i];
    }
    taskCsv << endl;
    ratioCsv << endl;
    vector<Test *> tests;
    tests.push_back(new PRTATest(data, 123321, 'p'));
    tests.push_back(new PRTATest(data, 123321, 't'));
    tests.push_back(new PRTATest(data, 123321, 'k'));
    tests.push_back(new DPTATest(data, 3, 1000, -1));
    tests.push_back(new LPTATest(data, 4, 123321, 4));
    for (auto &test:tests) {
        taskCsv << test->name();
        ratioCsv << test->name();
        for (int tc : taskCount) {
            data->allocate(200, tc);
            cout << test->name() << endl;
            test->start()->calculateTravelCost()->calculateAssignedTaskRatio();
            taskCsv << "," << fixed << setprecision(2) << (test->travelCost / test->server->assignedTaskCount);
            ratioCsv << "," << fixed << setprecision(2) << test->server->assignedTaskCount;
        }
        taskCsv << endl;
        ratioCsv << endl;
    }
    ratioCsv.close();
    taskCsv.close();
}

void gslErrorHandle(const char *reason,
                    const char *file,
                    int line,
                    int gsl_errno) {
    cout << reason << endl;
}

int main() {
    gsl_set_error_handler_off();
//    gsl_set_error_handler(&gslErrorHandle);
    DataSource *data = (new DataSource)
            ->loadTask("../data/task_ld.csv")
            ->loadWorker("../data/worker_ld.csv")
            ->shuffle(3423423);
    testTravelCostChangingWorker(data);
//    testTransmissionChaningTask(data);
//    testDoubleIntegrationOverLaplaceDist();
}